package gbench.sandbox.tuple;


import org.junit.jupiter.api.Test;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 *
 */
public class JunitM3 {
    
    /**
     * 创建数组函数
     * @param clazz
     * @param <T>
     * @return 0,1,2,3,4,...
     */
    @SuppressWarnings("unchecked")
    public static <T> IntFunction<T[]> arr(final Class<T> clazz) {
        return n -> (T[]) Array.newInstance(clazz, n);
    }

    /**
     * @return
     */
    public static Stream<Integer> stm() {
        return Stream.iterate(0, i -> i + 1);
    }

    /**
     * @param maxSize
     * @return 0,1,2,3,4,...
     */
    public static Stream<Integer> stm(final Number maxSize) {
        return stm().limit(maxSize.longValue());
    }

    /**
     * @param maxSize
     * @return 0,1,2,3,4,...
     */
    public static Stream<Integer> stm(final Number maxSize,final Predicate<Integer> predicate) {
        return stm().limit(maxSize.longValue()).filter(predicate);
    }
    
    /**
     * 
     * @param <T>
     * @param n
     * @param t
     * @return 数组
     */
    public static <T> T[] RPTA(final int n,final T t) {
        @SuppressWarnings("unchecked")
        final var clazz = (Class<T>)t.getClass();
        return stm(n).map(i->t).toArray(arr(clazz));
    }
    
    /**
     * @param ts
     * @param <T>
     * @return 数组
     */
    @SafeVarargs
    public static <T> T[] A(final T... ts) {
        return ts;
    }

    /**
     * @param ts
     * @param <T>
     * @return 列表
     */
    @SafeVarargs
    public static <T> List<T> L(final T... ts) {
        return Arrays.asList(ts);
    }
    
    /**
     * 列表转数组
     * @param <T>
     * @param t 元素 列表
     * @return 数组
     */
    public static <T> T[] L2A(List<T> tt) {
        @SuppressWarnings("unchecked")
        final var clazz = tt.stream().filter(e->e!=null).map(e->(Class<T>)e.getClass()).findFirst().orElse((Class<T>)Object.class);
        return tt.stream().toArray(arr(clazz));
    }
    
    /**
    *
    * @param t
    * @param tt
    * @param <T>
    * @return
    */
   public static <T> T[] cons(final T t,final T[] tt){
       @SuppressWarnings("unchecked")
       final var clazz = (Class<T>) t.getClass();
       return Stream.of(Stream.of(t),Stream.of(tt)).flatMap(e->e).toArray(arr(clazz));
   }

    /**
     * @param tt
     * @param <T>
     * @return
     */
    public static <T> Tuple2<Integer, Integer> shape(final T[][] tt) {
        return new Tuple2<>(tt.length, Stream.of(tt).collect(Collectors.summarizingInt(t -> t.length)).getMax());
    }
    
    /**
     * 把一个生成一个矩阵类型数据结构。
     * 
     * @param <T>
     * @param m
     * @param n
     * @param fxy
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[][] ij_yield(final int m, final int n, final BiFunction<Integer, Integer, T> fxy,
            final Class<T> clazz) {
        final var final_clazz = clazz != null ? clazz
                : stm(m).flatMap(i -> stm(n).map(j -> fxy.apply(i, j))).filter(e -> e != null).findFirst()
                        .map(e -> (Class<T>) e.getClass()).orElse((Class<T>) Object.class);// 类型类
        return stm(m).map(i -> stm(n).map(j -> fxy.apply(i, j)).toArray(arr(final_clazz)))
                .toArray(size -> (T[][]) Array.newInstance(final_clazz, size, 1));
    }
   
    /**
     * 把一个生成一个矩阵类型数据结构。
     * 
     * @param <T>
     * @param m
     * @param n
     * @param fxy
     * @return
     */
    public static <T> T[][] ij_yield(final int m, final int n, final BiFunction<Integer, Integer, T> fxy) {
        return ij_yield(m, n, fxy, null);
    }

    /**
     * 生成一个 把一维向量转换成 二维矩阵的函数:Vector 2 Matirx : 行顺序优先
     * 
     * @param <T>
     * @param height 行数
     * @param width  列数
     * @return 二维矩阵
     */
    @SuppressWarnings("unchecked")
    public static <T> Function<T[], T[][]> vt2mx(final int height, final int width) {
        return aa -> stm(height).map(i -> stm(width).map(j -> aa[(i * width + j) % aa.length])
                .toArray(arr(aa.getClass().getComponentType()))).toArray(arr((Class<T[]>) aa.getClass()));
    }
    
    /**
     * 生成一个 把一维向量转换成 二维矩阵的函数:Vector 2 Matirx ：列顺序优先
     * @param <T>
     * @param height 行数
     * @param width 列数
     * @return 二维矩阵
     */
    @SuppressWarnings("unchecked")
    public static <T> Function<T[],T[][]> vt2mx2(final int height,final int width ){
        return tt->ij_yield(height,width,(i,j)->tt[(j*height+i)%tt.length],(Class<T>)tt.getClass().getComponentType());
    }
    
    /**
     * 矩阵搜集器 向量类型为 列表
     * 
     * @param <T>
     * @param v2m 向量转数组的函数: vv->mm
     * @return 向量搜集器
     */
    public static <T> Collector<T, List<T>, T[][]> mxclc(final Function<List<T>, T[][]> v2m ) {
        return Collector.of(LinkedList::new, List::add, (aa, bb) -> {aa.addAll(bb);return aa;}, v2m);
    }
    
    /**
     * 矩阵搜集器： 向量类型为 数组
     * 
     * @param <T>
     * @param v2m 向量转数组的函数: vv->mm
     * @return 向量搜集器
     */
   public static <T> Collector<T, List<T>,T[][]> mxclc2( final Function<T[],T[][]> v2m ){
       return mxclc(ll->v2m.apply(L2A(ll)));
   }

    /**
     * @param <T>
     * @param <U>
     */
    public static class Tuple2<T, U> {

        /**
         * @param t
         * @param u
         */
        public Tuple2(final T t, final U u) {
            _1 = t;
            _2 = u;
        }

        public final T _1;
        public final U _2;

        /**
         * @param tt
         * @param uu
         * @param <T>
         * @param <U>
         * @return
         */
        public static <T, U> Stream<Tuple2<T, U>> zip(final T[] tt, final U[] uu) {
            return stm(Math.max(tt.length, uu.length)).map(i -> new Tuple2<>(tt[i % tt.length], uu[i % uu.length]));
        }
    }

    /**
     * @param <T>
     */
    public static class Elem<T> extends Tuple2<T, Elem<T>> {

        /**
         * @param t
         * @param parent
         */
        public Elem(final T t, final Elem<T> parent) {
            super(t, parent);
        }

        /**
         * @param t
         */
        public Elem(final T t) {
            super(t, null);
        }

        /**
         * @param elem
         * @param <U>
         * @return
         */
        public static <U> List<U> data(Elem<U> elem) {
            return elem._2 == null
                    ? Stream.of(elem._1).collect(Collectors.toList())
                    : Stream.of(data(elem._2), Arrays.asList(elem._1)).flatMap(e -> e.stream()).collect(Collectors.toList());
        }

        /**
         * @return
         */
        public List<T> data() {
            return data(this);
        }
    }

    /**
     * @param sss
     * @param <T>
     * @return
     */
    public static <T> Stream<Elem<T>> cph(final List<T[]> sss) {
        return sss.size() < 2
                ? Stream.of(sss.get(sss.size() - 1)).map(Elem::new)
                : cph(sss.subList(0, sss.size() - 1)).flatMap(p -> Stream.of(sss.get(sss.size() - 1)).map(t -> new Elem<>(t, p)));
    }

    

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double dot(final T[] tt, final U[] uu) {
        return Tuple2.zip(tt, uu).map(p -> p._1.doubleValue() * p._2.doubleValue()).reduce(0d, (a, b) -> a + b);
    }

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] add(final T[] tt, final U[] uu) {
        return Tuple2.zip(tt, uu).map(p -> p._1.doubleValue() + p._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] sub(final T[] tt, final U[] uu) {
        return Tuple2.zip(tt, uu).map(p -> p._1.doubleValue() - p._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] mul(final T[] tt, final U[] uu) {
        return Tuple2.zip(tt, uu).map(p -> p._1.doubleValue() * p._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] div(final T[] tt, final U[] uu) {
        return Tuple2.zip(tt, uu).map(p -> p._1.doubleValue() / p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] add(final T t, final U[] uu) {
        return Tuple2.zip(A(t), uu).map(p -> p._1.doubleValue() + p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] sub(final T t, final U[] uu) {
        return Tuple2.zip(A(t), uu).map(p -> p._1.doubleValue() - p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] mul(final T t, final U[] uu) {
        return Tuple2.zip(A(t), uu).map(p -> p._1.doubleValue() * p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[] div(final T t, final U[] uu) {
        return Tuple2.zip(A(t), uu).map(p -> p._1.doubleValue() / p._2.doubleValue()).toArray(Double[]::new);
    }

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] add(final T[][] tt, final U[][] uu) {
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() + uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] sub(final T[][] tt, final U[][] uu) {
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() - uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] mul(final T[][] tt, final U[][] uu) {
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() * uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] div(final T[][] tt, final U[][] uu) {
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() / uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }

    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] add(final T t, final U[][] uu) {
        final var tt = RPTA(1,A(t));
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() + uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] sub(final T t, final U[][] uu) {
        final var tt = RPTA(1,A(t));
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() - uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] mul(final T t, final U[][] uu) {
        final var tt = RPTA(1,A(1));
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() * uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] div(final T t, final U[][] uu) {
        final var tt = RPTA(1,A(t));
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() / uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }
    
    /**
     * @param t
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] div2(final T[][] tt, final U u) {
        final var uu = RPTA(1,A(u));
        final var shape_t = shape(tt);
        final var shape_u = shape(uu);
        final var shape = new Tuple2<>(Math.max(shape_t._1, shape_t._1),Math.max(shape_t._2, shape_t._2));
        
        return stm(shape._1).map(i->stm(shape._2)
                .map(j->tt[i%shape_t._1][j%shape_t._2].doubleValue() / uu[i%shape_u._1][j%shape_u._2].doubleValue())
                .toArray(Double[]::new))
                .toArray(Double[][]::new);
    }

    /**
     * @param tt
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[][] transpose(final T[][] tt) {
        final var shape = shape(tt);
        final Class<T[]> c1 = (Class<T[]>) tt.getClass().getComponentType();
        final var c2 = c1.getComponentType();
        return stm(shape._2).map(i -> stm(shape._1).map(j -> tt[j][i]).toArray(arr(c2))).toArray(arr(c1));
    }

    /**
     * @param tt
     * @param uu
     * @param <T>
     * @param <U>
     * @return
     */
    public static <T extends Number, U extends Number> Double[][] mmult(final T[][] tt, final U[][] uu) {
        final var ss = transpose(uu);
        final var shape = new Tuple2<>(shape(tt)._1, shape(uu)._2);
        return stm(shape._1).map(i -> stm(shape._2)
                    .map(j -> dot(tt[i], ss[j]))
                    .toArray(Double[]::new))
               .toArray(Double[][]::new);
    }

    /**
     * 余子式[矩阵]
     * @param tt
     * @param _i 行索引 从0开始 
     * @param _j 列索引 从0开始
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends Number> T[][] minor( final T[][]tt,final int _i, final int _j ){
        final var shape = shape(tt);
        final Class<T[]> c1 = (Class<T[]>)tt.getClass().getComponentType();
        final var c2 = c1.getComponentType();
        return stm(shape._1,i->i!=_i).map(i->stm(shape._2,j->j!=_j)
                    .map(j->tt[i][j])
                    .toArray(arr(c2)))
               .toArray(arr(c1));
    }

    /**
     * 行列式
     * @param tt
     * @param <T>
     * @return
     */
    public static <T extends Number> Double det(final T[][] tt){
        final var shape = shape(tt);
        return shape._1 == 1 && shape._2 == 1
            ? tt[0][0].doubleValue()
            : stm(shape._1).map(i->tt[i][0].doubleValue()*det(minor(tt,i,0))*Math.pow(-1,i)).reduce(0d,(a,b)->a+b);// 按列展开
    }
    
    /**
     * 伴随矩阵
     * @param <T>
     * @param tt
     * @return
     */
    public static <T extends Number> Double[][] adjugate(final T[][] tt){
        final var shape = shape(tt);
        return shape._1 == 1 && shape._2 == 1 
                ? RPTA(1,A(1/tt[0][0].doubleValue()))
                : stm(shape._1).map(i->stm(shape._2)
                        .map(j->det(minor(tt,j,i))*Math.pow(-1d,i+j))
                        .toArray(Double[]::new))
                  .toArray(Double[][]::new);
    }
    
    /**
     * 矩阵的逆
     * @param <T>
     * @param tt
     * @return
     */
    public static <T extends Number> Double[][] inverse(final T[][] tt) {
        return div2(adjugate(tt),det(tt));
    }

    /**
     * 施密特正交化
     * @param alphas
     * @param <T>
     * @return
     */
    public static <T extends Number> List<Double[]> schmidt(final List<T[]> alphas) {
        final var betas = new LinkedList<Double[]>();
        final var eps = 1e-10;// 微小忽略量
        final Predicate<Number[]> is_not_zero = aa -> Stream.of(aa).allMatch(e -> Math.abs(e.doubleValue()) >= eps);// 与微小忽略量进行比较
        alphas.stream().filter(is_not_zero)
            .map( alpha -> betas.stream()
                .map( beta -> mul(-dot(alpha, beta) / dot(beta, beta), beta))
                .reduce((x,y)->add(x,y)).map(e -> add(alpha, e))
                .orElse(Stream.of(alpha).map(e -> e.doubleValue()).toArray(Double[]::new))
            ).filter(is_not_zero).forEach(betas::add);
        return betas;
    }

    /**
     * @param tt
     * @param <T>
     * @return
     */
    public static <T> String fmt(final T[][] tt) {
        return Stream.of(tt).map(t -> Stream.of(t).map(e -> e + "").collect(Collectors.joining("\t")))
                .collect(Collectors.joining("\n"));
    }

    /**
     * 测试程序
     */
    @Test
    public void foo() {
        final var digits = "0123456789".split("");
        System.out.println("-------cph(3)--------");
        cph(L(digits, digits, digits)).map(e -> e.data()).forEach(System.out::println);

        final var aa = A(1, 2);
        final var bb = A(aa, aa);
        System.out.println("mmult:\n"+fmt(mmult(bb, bb)));
        System.out.println("-------schmidt--------");
        schmidt(L(aa, aa)).forEach(b -> {
            System.out.println(Arrays.asList(b));
        });

        System.out.println("--------代数余子式--------------");
        System.out.println(fmt(minor(bb,0,0)));

        System.out.println("----------------------");
        final var mm = A(A(1,2),A(3,4));
        System.out.println(fmt(mm));
        System.out.println("det:"+det(mm));
        
        System.out.println("----------矩阵的逆矩阵------------");
        System.out.println(fmt(mmult(mm,inverse(mm))));
        
        System.out.println("----------vt2mx------------");
        System.out.println(fmt(Stream.of(1,2,3).collect(mxclc2(vt2mx(3,3)))));
        System.out.println("----------vt2mx2------------");
        System.out.println(fmt(Stream.of(1,2,3).collect(mxclc2(vt2mx2(3,3)))));
        
    }

}
